/*    This file is part of nit.
 *
 *    Nit is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    Nit is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with nit.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>

#include "munit/munit.h"

#include "../list.h"
#include "../set.h"
#include "../map.h"
#include "../graph.h"
#include "../index.h"
#include "../trie.h"
#include "../state.h"
#include "../symbol.h"

static MunitResult
test_list(const MunitParameter params[], void *data)
{
	List list;
	List_entry *entry;

	list_init(&list, NULL);
	list_add(&list, NULL, "\n");
	list_add(&list, NULL, "world");
	list_add(&list, NULL, "Hello, ");

	list_foreach (entry, &list)
		printf("%s", entry->data);

	list_dispose(&list);
	return MUNIT_OK;
}

static void
dispose_free(void *ptr, void *extra)
{
	free(ptr);
}

static MunitResult
test_set(const MunitParameter params[], void *data)
{
	Disposer disposer = { .dispose = dispose_free, .extra = NULL };
	Set set;
	Set_entry *entry;
	int i;
	int bin;
	List list;
	List_entry *lentry;

        set_init(&set, &disposer, 0);

	for (i = 0; i < 100; ++i) {
		int *j = malloc(sizeof(*j));

		*j = i;
		set_add(&set, sizeof(i), j);
	}

	for (i = 0; i < 100; ++i) {
		int *j = &i;

		munit_assert_int(1, ==,
				 set_get(&set, sizeof(i), (void **) &j));
		munit_assert_int(i, ==, *j);
	}

	i = 0;

	set_foreach (&set, bin, entry) {
		munit_assert_int(100, >, *(int *) entry->dat);
		++i;
	}

	set_list(&set, &list, 1, 0);

	i = 0;

	list_foreach(lentry, &list) {
		Set_key *key = lentry->data;

		munit_assert_int(i, ==, *(int *) key->dat);
		++i;
	}

	munit_assert_int(i, ==, 100);
	list_dispose(&list);
	set_dispose(&set, NULL);
	return MUNIT_OK;
}

static void
map_free_contents(void *key, void *storage, void *extra)
{
	free(storage);
}

static MunitResult
test_map(const MunitParameter params[], void *data)
{
	Disposer disposer = { .dispose = dispose_free, .extra = NULL };
	Map map;
	Set_entry *entry;
	int bin;
	int i;

	map_init(&map, &disposer, 0);

	for (i = 0; i < 100; ++i) {
		int *j = malloc(sizeof(*j));

		*j = i;
		map_add(&map, sizeof(i), &i, j);
	}

	for (i = 0; i < 100; ++i) {
		int *j;

		munit_assert_int(1, ==,
				 map_get(&map, sizeof(i), &i, (void **) &j));
		munit_assert_int(i, ==, *j);
	}

	i = 0;

        map_foreach (&map, bin, entry) {
		int *j = map_val(entry->dat, entry->key_len);

		munit_assert_int(100, >, *j);
		++i;
	}

	munit_assert_int(i, ==, 100);
	set_dispose(&map, NULL);
	return MUNIT_OK;
}

static MunitResult
test_graph(const MunitParameter params[], void *data)
{
	Graph graph;
	Graph_node *node;
	Graph_node *node2;
	Graph_node *node3;
	Set_key key;

	graph_init(&graph, NULL, 1);
	node = graph_node(&graph, "Hello");
	node2 = graph_node(&graph, "world!\n");
	munit_assert_false(graph_linked(node, node2, &key));

	graph_join(node, node2, sizeof(", "), ", ");
	munit_assert_true(graph_linked(node, node2, &key));

	node3 = graph_get(node, sizeof(", "), ", ");
	munit_assert_ptr(node2, ==, node3);
	graph_disjoin(node, NULL, sizeof(", "), ", ");
	munit_assert_false(graph_linked(node, node2, &key));

	graph_dispose(&graph);
	return MUNIT_OK;
}

static MunitResult
test_index(const MunitParameter params[], void *data)
{
	Index index;
	Set set;
	List *list;

	index_init(&index, 0);
	set_init(&set, NULL, 0);
	set_add(&set, sizeof("hi"), "hi");
	set_add(&set, sizeof("hello"), "hello");
	set_add(&set, sizeof("hola"), "hola");
	index_add(&index, &set, "Bye");

	set_dispose(&set, NULL);
	set_init(&set, NULL, 0);
	set_add(&set, sizeof("wow!"), "wow!");
	set_add(&set, sizeof("woah!"), "woah!");
	set_add(&set, sizeof("wonderful!"), "wonderful!");
	index_add(&index, &set, "huh");
	set_dispose(&set, NULL);

	index_get(&index, sizeof("hi"), "hi", &list);
	munit_assert_string_equal("Bye", *list_peek(list));
	index_get(&index, sizeof("wow!"), "wow!", &list);
	munit_assert_string_equal("huh", *list_peek(list));
	map_dispose(&index);
	return MUNIT_OK;
}

static MunitResult
test_trie(const MunitParameter params[], void *data)
{
	Trie trie;
	Set set;
	List list;
	const char *err_str;
	void *val;

	trie_init(&trie, NULL, NULL);
	set_init(&set, NULL, 0);
	set_add(&set, sizeof("hi"), "hi");
	set_add(&set, sizeof("hello"), "hello");
	set_add(&set, sizeof("hola"), "hola");
	set_list(&set, &list, 1, 0);
	trie_list_add(&trie, NULL, &list, "Hi!");
	munit_assert_string_equal(trie_list_get(trie.root,
						&list)->dat, "Hi!");
	trie_list_remove(&trie, NULL, &list, &val);
	munit_assert_null(trie_list_get(trie.root, &list));
	list_dispose(&list);
	set_dispose(&set, NULL);
	trie_dispose(&trie);
	return MUNIT_OK;
}

void *
add(int real, void *dat, void *args, void *extra)
{
        int *num = dat;
	int *num2 = extra;

	(void) args;
	(void) real;
	*num += *num2;
	return num2;
}

void *
subtract(void *res, void *dat, void *args, void *extra)
{
	int *num = dat;
	int *num2 = res;

	(void) extra;
	(void) args;
	*num -= *num2;
	return NULL;
}

static MunitResult
test_state(const MunitParameter params[], void *data)
{
	Acts acts;
	State state;
	int num = 0;
	int one = 1;

	acts_init(&acts);
	acts_add(&acts, sizeof("add"), "add", add, subtract, NULL);
	state_init(&state, &acts, 0, NULL, &one);
	munit_assert_int(0, ==,
			 state_do(&state, sizeof("add"), "add",
				  &num, NULL, 0));
	munit_assert_int(num, ==, 1);
	state_undo(&state);
	munit_assert_int(num, ==, 0);
	state_redo(&state, 0, 0);
	munit_assert_int(num, ==, 1);
	state_dispose(&state);
	acts_dispose(&acts);
	return MUNIT_OK;
}

static MunitResult
test_symbol(const MunitParameter params[], void *data)
{
	Symtbl tbl;
	Symbol sym;
	Symbol sym2;

	symtbl_init(&tbl);
	symtbl_symbol(&tbl, &sym, "Cats");
	munit_assert_string_equal(sym, "Cats");
	symtbl_symbol(&tbl, &sym2, "Cats");
	munit_assert_ptr_equal(sym, sym2);
	symtbl_dispose(&tbl);
	return MUNIT_OK;
}

static MunitTest test_suite_tests[] = {
	{ (char *) "/graph", test_graph,
	  NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
	{ (char *) "/list", test_list,
	  NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
	{ (char *) "/set", test_set,
	  NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
	{ (char *) "/map", test_map,
	  NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
	{ (char *) "/index", test_index,
	  NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
	{ (char *) "/trie", test_trie,
	  NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
	{ (char *) "/state", test_state,
	  NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
	{ (char *) "/symbol", test_symbol,
	  NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
	{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }

};

static const MunitSuite test_suite = {
	(char *) "nit",
	test_suite_tests,
	NULL,
	1,
	MUNIT_SUITE_OPTION_NONE
};

int
main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc + 1)])
{
	/* test_list(NULL, NULL); */
	/* test_set(NULL, NULL); */
	/* test_graph(NULL, NULL); */
	/* test_index(NULL, NULL); */
	/* test_trie(NULL, NULL); */
	/* test_state(NULL, NULL); */
	test_symbol(NULL, NULL);
	return 0;
	/* return munit_suite_main(&test_suite, NULL, argc, argv); */
}
